package base

import (
	"bytes"
	"database/sql"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/jinzhu/gorm"
	"reflect"
	"store/bootstrap"
	"store/libs/helper"
	"store/libs/log"
	"store/models"
	"strings"
	"time"
)

type Field struct {
	Name      string
	BigCase   string
	SmallCase string
	Type      reflect.Kind
	Length    int64
	Unsigned  bool
	Default   string
	Comment   string
}

type CommonModel struct {
	Ctx *gin.Context
}

func NewCommonModel(ctx *gin.Context) *CommonModel {
	return &CommonModel{
		Ctx: ctx,
	}
}

/**
 * where条件转换
 */
func addWhere(db *gorm.DB, where map[string]interface{}) *gorm.DB {
	for field, param := range where {
		//如果未绑定参数
		if strings.Index(field, "?") < 0 {
			if inWhere, ok := param.([]interface{}); ok {
				db = db.Where(helper.Append(field, " IN (?)"), inWhere)
			} else {
				db = db.Where(helper.Append(field, " = ?"), param)
			}
			continue
		}

		//如果已绑定参数
		db = db.Where(field, param)
	}
	return db
}

/**
 * order条件转换
 */
func addOrder(db *gorm.DB, orders []string) *gorm.DB {
	if len(orders) < 1 {
		return db
	}

	orderBuf := bytes.NewBuffer(nil)

	for _, order := range orders {
		if order[0:1] == "-" {
			helper.Write2Buffer(orderBuf, order[1:], " DESC,")
			continue
		}
		helper.Write2Buffer(orderBuf, strings.TrimPrefix(order, "+"), " ASC,")
	}

	return db.Order(strings.TrimSuffix(orderBuf.String(), ","))
}

/**
 * 查询结果转换为map[string]string切片
 */
func Rows2MapList(ctx *gin.Context, rows *sql.Rows) []map[string]string {
	defer func() {
		err := rows.Close()
		if err != nil {
			log.Error(ctx, err.Error())
		}
	}()

	cols, _ := rows.Columns()
	vals := make([][]byte, len(cols))
	scans := make([]interface{}, len(cols))
	for k, _ := range vals {
		scans[k] = &vals[k]
	}

	list := []map[string]string{}
	for rows.Next() {
		err := rows.Scan(scans...)
		if err != nil {
			log.Error(ctx, err.Error())
			return []map[string]string{}
		}

		row := make(map[string]string)
		for k, v := range scans {
			key := cols[k]
			row[key] = string(*(v.(*[]byte)))
		}
		list = append(list, row)
	}

	return list
}

func AnalysisTable(tableInfo []byte) map[string]Field {
	left := []byte("(")
	right := []byte(")")
	start := bytes.Index(tableInfo, left) + 1
	end := bytes.LastIndex(tableInfo, right)
	fields := bytes.Split(tableInfo[start:end], []byte(","))

	fieldList := map[string]Field{}
	for _, value := range fields {
		isField := bytes.Index(value, []byte("`"))
		//非字段
		if isField > 3 {
			continue
		}

		parts := bytes.Split(value, []byte(" "))
		if len(parts) < 5 {
			continue
		}

		name := string(bytes.Trim(parts[2], "`"))

		f := Field{
			Name:    name,
			BigCase: helper.Line2BigCase(name, "_"),
		}

		f.SmallCase = string(helper.LowerFirst(f.BigCase))

		lenStart := bytes.Index(parts[3], left)
		if lenStart < 0 {
			lenStart = len(parts[3])
		} else {
			f.Length = helper.ToInt(string(parts[3][lenStart+1 : len(parts[3])-1]))
		}

		for index, part := range parts {
			switch string(part) {
			case "DEFAULT":
				f.Default = string(bytes.Trim(bytes.Trim(parts[index+1], "'"), "()"))
			case "COMMENT":
				f.Comment = string(bytes.Trim(parts[index+1], "'"))
			case "unsigned":
				f.Unsigned = true
			}
		}

		kind := string(parts[3][0:lenStart])
		switch kind {
		case "tinyint":
			f.Type = reflect.Int8
			if f.Unsigned {
				f.Type = reflect.Uint8
			}
		case "smallint":
			f.Type = reflect.Int16
			if f.Unsigned {
				f.Type = reflect.Uint16
			}
		case "mediumint":
			f.Type = reflect.Int32
			if f.Unsigned {
				f.Type = reflect.Int32
			}
		case "int", "bigint":
			f.Type = reflect.Int64
			if f.Unsigned {
				f.Type = reflect.Uint64
			}
		case "double", "float", "numeric", "decimal":
			f.Type = reflect.Float64
		default:
			f.Type = reflect.String
		}

		fieldList[name] = f
	}

	return fieldList
}

func (this *CommonModel) AllTables() []string {
	rows, err := models.Orm.DB().Query("SHOW TABLES")
	if err != nil {
		fmt.Println(err)
		return nil
	}
	rowsMap := Rows2MapList(this.Ctx, rows)
	if len(rowsMap) > 0 {
		tableList := []string{}
		for _, tableRow := range rowsMap {
			for _, table := range tableRow {
				tableList = append(tableList, table)
			}
		}

		return tableList
	}
	return nil
}

func (this *CommonModel) TableInfo(table string) []byte {
	rows, err := models.Orm.DB().Query(helper.Append("SHOW CREATE TABLE ", models.Orm.Dialect().Quote(table)))
	if err != nil {
		return nil
	}

	infoMap := Rows2MapList(this.Ctx, rows)
	if len(infoMap) > 0 {
		infoBuf := bytes.NewBufferString("")
		for _, val := range infoMap[0] {
			if val != table {
				infoBuf.WriteString(val)
			}
		}
		return infoBuf.Bytes()
	}
	return nil
}

func (this *CommonModel) TableExists(table string) bool {
	return models.Orm.HasTable(table)
}

/**
 * 优化表
 */
func (this *CommonModel) OptimizeTable(table string) error {
	return models.Orm.Exec("OPTIMIZE TABLE ?", table).Error
}

/**
 * 用于释放空间
 */
func (this *CommonModel) Alter2Innodb(table string) error {
	return models.Orm.Exec("ALTER TABLE ? engine=INNODB", table).Error
}

func (this *CommonModel) Create(table string, info map[string]interface{}) int64 {
	args := []interface{}{}
	fields := []string{}
	values := []string{}
	for field, value := range info {
		fields = append(fields, helper.Append("`", field, "`"))
		args = append(args, value)
		values = append(values, "?")
	}

	s := helper.Append("INSERT INTO `", table, "`(", strings.Join(fields, ","), ")VALUES(", strings.Join(values, ","), ")")
	stmt, err := models.Orm.DB().Prepare(s)
	if err != nil {
		log.Error(this.Ctx, err.Error())
		return 0
	}

	result, err := stmt.Exec(args...)
	if err != nil {
		log.Error(this.Ctx, err.Error())
		return 0
	}

	id, err := result.LastInsertId()
	if err != nil {
		log.Error(this.Ctx, err.Error())
		return 0
	}
	return id
}

/**
 * 根据Id删除单条记录
 */
func (this *CommonModel) Delete(table string, id string) int64 {
	return models.Orm.Exec(helper.Append("DELETE FROM `", table, "` WHERE id = ? LIMIT 1"), id).RowsAffected
}

/**
 * 更新
 */
func (this *CommonModel) Update(table string, where map[string]interface{}, info map[string]interface{}) int64 {
	info["update_time"] = time.Now().Unix()
	db := models.Orm.Table(table)
	return addWhere(db, where).Update(info).RowsAffected
}

/**
 * 乐观锁更新
 */
func (this *CommonModel) UpdateWithLock(table string, id string, version uint64, info map[string]interface{}) int64 {
	info["version"] = version + 1
	return this.Update(table,
		map[string]interface{}{
			"id":      id,
			"version": version,
		},
		info,
	)
}

/**
 * 禁用启用,返回受影响行数
 */
func (this *CommonModel) UpOrDown(table string, id string, value uint8) int64 {
	return models.Orm.Table(table).Where("id = ? ", id).Update("is_on", value).RowsAffected
}

/**
 * 根据where查询单条记录详情,只返回第一个符合条件的记录
 */
func (this *CommonModel) Info(table string, where map[string]interface{}) map[string]string {
	db := models.Orm.Table(table)
	db = addWhere(db, where)

	info := make(map[string]string)
	rows, err := db.Limit(1).Rows()
	if err != nil {
		log.Error(this.Ctx, err.Error())
		return info
	}

	rowsList := Rows2MapList(this.Ctx, rows)
	if len(rowsList) > 0 {
		info = rowsList[0]
	}

	return info
}

/**
 * 列表分页检索,支持where和order，无where表示所有
 * where  传参方式：{"id":[5, 6, 7], "type":5}   => WHERE id IN('5','6','7') AND type=5
 * orders 传参方式：["-id", "add_time", "+type"] => ORDER BY id DESC,add_time ASC,type ASC
 */
func (this *CommonModel) Index(table string, page int64, pageSize int64, where map[string]interface{}, orders []string) map[string]interface{} {
	if page < 1 {
		page = 1
	}

	minPageSize := bootstrap.ParamInt("minPageSize")
	maxPageSize := bootstrap.ParamInt("maxPageSize")

	if pageSize < minPageSize {
		pageSize = minPageSize
	}

	if pageSize > maxPageSize {
		pageSize = maxPageSize
	}

	db := models.Orm.Table(table)
	db = addWhere(db, where)

	var count int64
	db.Count(&count)

	result := map[string]interface{}{
		"page":       page,
		"pageSize":   pageSize,
		"totalCount": count,
		"totalPage":  helper.Ceil(count, pageSize),
		"list":       []map[string]string{},
	}

	if count > 0 || page > result["totalPage"].(int64) {
		rows, err := addOrder(db, orders).Offset((page - 1) * pageSize).Limit(pageSize).Rows()
		if err == nil {
			result["list"] = Rows2MapList(this.Ctx, rows)
		}
	}

	return result
}
